#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/5/5 22:47
# @Author : 侯建军
# @Site : huoniu8.com
# @File : BikeML.py
# @Software: PyCharm

import pandas as pd
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.model_selection import train_test_split

def fit_transform_ohe(df, col_name):
    """
    该函数对于指定的列进行独热编码。

    Args:
        df(pandas.DataFrame): 含有目标数据的dataframe
        col_name: 需要进行独热编码的字段
    Returns:
        tuple: label_encoder, one_hot_encoder, transformed column as pandas Series
    """
    # 1. 首先转换成数值型编码
    le = LabelEncoder()
    le_labels = le.fit_transform(df[col_name])
    df[col_name + '_label'] = le_labels

    # 2. 将数值型编码转成独热编码
    ohe = OneHotEncoder()
    feature_attr = ohe.fit_transform(df[[col_name + '_label']]).toarray()
    feature_labels = [col_name + '_' + str(cls_label) for cls_label in le.classes_]
    features_df = pd.DataFrame(feature_attr, columns=feature_labels)
    return le, ohe, features_df


def transform_ohe(df, le, ohe, col_name):
    """
    对于给定的列，使用目标编码器对其进行独热编码

    Args:
        df(pandas.DataFrame): 含有目标数据的dataframe
        le(Label Encoder): 标签编码器
        ohe(One Hot Encoder): 独热编码器
        col_name: 需要进行独热编码的字段

    Returns:
        tuple: transformed column as pandas Series

    """
    # 1.首先转换成数值型编码
    col_labels = le.transform(df[col_name])
    df[col_name + '_label'] = col_labels

    # 2. 将数值型编码转成独热编码
    feature_arr = ohe.fit_transform(df[[col_name + '_label']]).toarray()
    feature_labels = [col_name + '_' + str(cls_label) for cls_label in le.classes_]
    features_df = pd.DataFrame(feature_arr, columns=feature_labels)

    return features_df

# 1. 数据处理
hour_df  = pd.read_csv('../data/bike/hour.csv', header=0)
# 自定义数据字段
hour_df.rename(columns={'instant':'rec_id',
                      'dteday':'datetime',
                      'holiday':'is_holiday',
                      'workingday':'is_workingday',
                      'weathersit':'weather_condition',
                      'hum':'humidity',
                      'mnth':'month',
                      'cnt':'total_count',
                      'hr':'hour',
                      'yr':'year'},
                      # inplace=True：不创建新的对象，直接对原始对象进行修改；
                      # inplace=False：对数据进行修改，创建并返回新的对象承载其修改结果。
                      inplace=True)


# 对日期类型转换
hour_df['datetime'] = pd.to_datetime(hour_df.datetime)

# 枚举类型category类别属性
hour_df['season'] = hour_df.season.astype('category')
hour_df['is_holiday'] = hour_df.is_holiday.astype('category')
hour_df['weekday'] = hour_df.weekday.astype('category')
hour_df['weather_condition'] = hour_df.weather_condition.astype('category')
hour_df['is_workingday'] = hour_df.is_workingday.astype('category')
hour_df['month'] = hour_df.month.astype('category')
hour_df['year'] = hour_df.year.astype('category')
hour_df['hour'] = hour_df.hour.astype('category')

X, X_test, y, y_test = train_test_split(hour_df.iloc[:,0:-3], # 最后一列是目标变量，倒数2、3是未注册用户和注册用户，并不是可用的特征
                                        hour_df.iloc[:,-1],
                                        test_size=0.33,
                                        random_state=42)
X.reset_index(inplace=True)
y = y.reset_index()

X_test.reset_index(inplace=True)
y_test = y_test.reset_index()

cat_attr_list = ['season','is_holiday',
                 'weather_condition','is_workingday',
                 'hour','weekday','month','year']
numeric_feature_cols = ['temp','humidity','windspeed','hour','weekday','month','year']
subset_cat_features =  ['season','is_holiday','weather_condition','is_workingday']


encoded_attr_list = []
for col in cat_attr_list:
    return_obj = fit_transform_ohe(X,col)
    encoded_attr_list.append({'label_enc':return_obj[0],
                              'ohe_enc':return_obj[1],
                              'feature_df':return_obj[2],
                              'col_name':col})

# 将待用的特征放在一起
feature_df_list = [X[numeric_feature_cols]]
feature_df_list.extend([enc['feature_df'] \
                            for enc in encoded_attr_list \
                                if enc['col_name'] in subset_cat_features])
train_df_new = pd.concat(feature_df_list, axis=1)
print("Shape:{}".format(train_df_new.shape))

train_df_new.head()

# 2.线性回归
X = train_df_new
y = y.total_count.values.reshape(-1,1)

from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()

from sklearn.model_selection import cross_val_predict
predicted = cross_val_predict(lin_reg, X, y, cv=10)

# 3. 可视化
import matplotlib.pyplot as plt
import seaborn as sn

params = {
    'legend.fontsize': 'x-large',
    'figure.figsize': (15, 8),     # 设置seaborn大小
    'axes.labelsize': 'x-large',
    'axes.titlesize':'x-large',
    'xtick.labelsize':'x-large',
    'ytick.labelsize':'x-large',
    'font.sans-serif':'SimHei',     # 显示中文
    'axes.unicode_minus':False
}

sn.set_style('whitegrid')
sn.set_context('talk')
plt.rcParams.update(params)         # 使用我们的参数生效

# fig, ax = plt.subplots()
# ax.scatter(y, y-predicted)
# ax.axhline(lw=2,color='black')
# ax.set_xlabel(u'真实值')
# ax.set_ylabel(u'残差')
# plt.show()

from sklearn.model_selection import cross_val_score
import numpy as np

r2_scores = cross_val_score(lin_reg, X, y, cv=10) # 默认采用R^2
mse_scores = cross_val_score(lin_reg, X, y, cv=10,scoring='neg_mean_squared_error') # 效用函数：负均方误差

# fig, ax = plt.subplots()
# ax.plot([i for i in range(len(r2_scores))],r2_scores,lw=2)
# ax.set_xlabel('Iteration')
# ax.set_ylabel('R-Squared')
# ax.title.set_text("较差验证得分, Avg:{}".format(np.average(r2_scores)))
# plt.show()

print("R-squared:{}".format(r2_scores.mean()))
print("RMSE::{}".format(np.sqrt(-mse_scores).mean()))
print("y mean:", y.mean())
lin_reg.fit(X,y) # cross_val_score本身不会影响lin_reg

# 4. 测试
test_encoded_attr_list = []
for enc in encoded_attr_list:
    col_name = enc['col_name']
    le = enc['label_enc']
    ohe = enc['ohe_enc']
    test_encoded_attr_list.append({'feature_df': transform_ohe(X_test,
                                                               le, ohe,
                                                               col_name),
                                   'col_name': col_name})

test_feature_df_list = [X_test[numeric_feature_cols]]
test_feature_df_list.extend([enc['feature_df'] \
                             for enc in test_encoded_attr_list \
                             if enc['col_name'] in subset_cat_features])

test_df_new = pd.concat(test_feature_df_list, axis=1)
print("Shape::{}".format(test_df_new.shape))

test_df_new.head()

X_test = test_df_new
y_test = y_test.total_count.values.reshape(-1,1)

y_pred = lin_reg.predict(X_test)
residuals = y_test-y_pred

from sklearn.metrics import mean_squared_error

r2_score = lin_reg.score(X_test,y_test)
print("R-squared::{}".format(r2_score))
print("MSE: %.2f" % np.sqrt(mean_squared_error(y_test, y_pred)))

fig, ax = plt.subplots()
ax.scatter(y_test, residuals)
ax.axhline(lw=2,color='black')
ax.set_xlabel(u'真实值')
ax.set_ylabel(u'残差')
ax.title.set_text(u"$R^2$={}".format(np.average(r2_score)))
plt.show()


